package com.cts.rt.webapp.controller;

import org.apache.commons.lang.StringUtils;
import com.cts.rt.Constants;
import com.cts.rt.model.Employee;
import com.cts.rt.model.EmployeeLevel;
import com.cts.rt.model.Role;
import com.cts.rt.model.User;
import com.cts.rt.service.EmployeeLevelManager;
import com.cts.rt.service.RoleManager;
import com.cts.rt.service.UserExistsException;
import com.cts.rt.service.UserManager;
import com.cts.rt.webapp.util.RequestUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * Implementation of <strong>SimpleFormController</strong> that interacts with
 * the {@link UserManager} to retrieve/persist values to the database.
 * 
 * <p>
 * <a href="UserFormController.java.html"><i>View Source</i></a>
 * 
 * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a>
 */
@Controller
@RequestMapping("/userform*")
public class UserFormController extends BaseFormController {
	private EmployeeLevelManager employeeLevelManager;

	@Autowired
	public void setEmployeeLevelManager(EmployeeLevelManager roleManager) {
		this.employeeLevelManager = roleManager;
	}

	public UserFormController() {
		setCancelView("redirect:/mainMenu");
		setSuccessView("redirect:/admin/users");
	}

	@RequestMapping(method = RequestMethod.POST)
	public String onSubmit(Employee user, BindingResult errors,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (request.getParameter("cancel") != null) {
			if (!StringUtils.equals(request.getParameter("from"), "list")) {
				return getCancelView();
			} else {
				return getSuccessView();
			}
		}

		if (validator != null) { // validator is null during testing
			validator.validate(user, errors);

			if (errors.hasErrors() && request.getParameter("delete") == null) { // don't
																				// validate
																				// when
																				// deleting
				return "userform";
			}
		}

		log.debug("entering 'onSubmit' method...");

		Locale locale = request.getLocale();

		if (request.getParameter("delete") != null) {
			getEmployeeManager().removeEmployee(user.getId().toString());
			saveMessage(request,
					getText("user.deleted", user.getFullName(), locale));

			return getSuccessView();
		} else {

			// only attempt to change roles if user is admin for other users,
			// showForm() method will handle populating
			if (request.isUserInRole(Constants.ADMIN_ROLE)) {
				String[] userRoles = request.getParameterValues("userRoles");

				if (userRoles != null) {
					user.setEmployeeLevel(null);
					for (String roleName : userRoles) {
						user.setEmployeeLevel(employeeLevelManager
								.getEmployeeLevel(roleName));
					}
				}
			} else {
				// if user is not an admin then load roles from the database
				// (or any other user properties that should not be editable
				// by users without admin role)
				Employee cleanUser = getEmployeeManager().getUserByUsername(
						request.getRemoteUser());
				user.setEmployeeLevel(cleanUser.getEmployeeLevel());
			}

			Integer originalVersion = user.getVersion();

			try {
				getEmployeeManager().saveEmployee(user);
			} catch (AccessDeniedException ade) {
				// thrown by UserSecurityAdvice configured in aop:advisor
				// userManagerSecurity
				log.warn(ade.getMessage());
				response.sendError(HttpServletResponse.SC_FORBIDDEN);
				return null;
			} catch (UserExistsException e) {
				errors.rejectValue("username", "errors.existing.user",
						new Object[] { user.getUsername(), user.getEmail() },
						"duplicate user");

				// redisplay the unencrypted passwords
				user.setPassword(user.getConfirmPassword());
				// reset the version # to what was passed in
				user.setVersion(originalVersion);

				return "userform";
			}

			if (!StringUtils.equals(request.getParameter("from"), "list")) {
				saveMessage(request,
						getText("user.saved", user.getFullName(), locale));

				// return to main Menu
				return getCancelView();
			} else {
				if (StringUtils.isBlank(request.getParameter("version"))) {
					saveMessage(request,
							getText("user.added", user.getFullName(), locale));

					// Send an account information e-mail
					message.setSubject(getText("signup.email.subject", locale));

					try {
						sendUserMessage(
								user,
								getText("newuser.email.message",
										user.getFullName(), locale),
								RequestUtil.getAppURL(request));
					} catch (MailException me) {
						saveError(request, me.getCause().getLocalizedMessage());
					}

					return getSuccessView();
				} else {
					saveMessage(
							request,
							getText("user.updated.byAdmin", user.getFullName(),
									locale));
				}
			}
		}

		return "userform";
	}

	@ModelAttribute
	@RequestMapping(method = RequestMethod.GET)
	protected Employee showForm(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		// If not an administrator, make sure user is not trying to add or edit
		// another user
		if (!request.isUserInRole(Constants.ADMIN_ROLE)
				&& !isFormSubmission(request)) {
			if (isAdd(request) || request.getParameter("id") != null) {
				response.sendError(HttpServletResponse.SC_FORBIDDEN);
				log.warn("User '" + request.getRemoteUser()
						+ "' is trying to edit user with id '"
						+ request.getParameter("id") + "'");

				throw new AccessDeniedException(
						"You do not have permission to modify other users.");
			}
		}

		if (!isFormSubmission(request)) {
			String userId = request.getParameter("id");

			// if user logged in with remember me, display a warning that they
			// can't change passwords
			log.debug("checking for remember me login...");

			AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
			SecurityContext ctx = SecurityContextHolder.getContext();

			if (ctx.getAuthentication() != null) {
				Authentication auth = ctx.getAuthentication();

				if (resolver.isRememberMe(auth)) {
					request.getSession().setAttribute("cookieLogin", "true");

					// add warning message
					saveMessage(
							request,
							getText("userProfile.cookieLogin",
									request.getLocale()));
				}
			}

			Employee user;
			if (userId == null && !isAdd(request)) {
				user = getEmployeeManager().getUserByUsername(
						request.getRemoteUser());
			} else if (!StringUtils.isBlank(userId)
					&& !"".equals(request.getParameter("version"))) {
				user = getEmployeeManager().getEmployee(userId);
			} else {
				user = new Employee();
				user.setEmployeeLevel(new EmployeeLevel(Constants.USER_ROLE));
			}

			user.setConfirmPassword(user.getPassword());

			return user;
		} else {
			// populate user object from database, so all fields don't need to
			// be hidden fields in form
			return getEmployeeManager().getEmployee(request.getParameter("id"));
		}
	}

	private boolean isFormSubmission(HttpServletRequest request) {
		return request.getMethod().equalsIgnoreCase("post");
	}

	protected boolean isAdd(HttpServletRequest request) {
		String method = request.getParameter("method");
		return (method != null && method.equalsIgnoreCase("add"));
	}
}
